""" FileTools module """
from unittest import TestCase
import re
import os
import shutil

__all__ = ["Wildcard", "walk", "WalkFile"]

class Wildcard:
    """
        Wildcard with followin format:
        abcde           - simple text
        *               - anonymous wildcard (any number of characters)
        [name]          - named wildcard
        (a|b|c)         - branch, 'a', 'b' or 'c'
        a?              - previous
        %%, %\, ...     - registered symbol
    """
    __slots__ = ('re', 'names')

    def __init__(self, cond):
        regexp, names = translateWildcard(cond, True)
        self.re     = re.compile(regexp)
        self.names  = names

    def match(self, text):
        m = self.re.match(text)
        if m is None:
            return None

        return { p: m.group(p) for p in self.names }

class WalkFile:
    __slots__ = ('path', 'name', 'parts')

    def __init__(self, path, name, parts):
        self.path = path
        self.name = name
        self.parts = parts

    def __getattr__(self, name):
        if name in WalkFile.__slots__ or name.startswith('_'):
            return object.__getattribute__(self, name)

        parts = object.__getattribute__(self, "parts")
        return parts[name]

    def __setattr__(self, name, value):
        if hasattr(self, name):
            raise AssertionError("WalkFile properties are readonly")
        object.__setattr__(self, name, value)

def walk(path, walkDirs = False, walker = os.walk):
    """ Walk through dirs. Path format is described in Wildcard documentation. """
    r, s, f = _splitWalkPath(path)

    s = re.sub(r'(?P<prefix>^|\\)\*\\', r'\g<prefix>(*\\)?', s)

    path = r + s + f
    w = Wildcard(path)

    for root, dirs, files in walker(r):
        for name in (files if not walkDirs else dirs):
            path = _normalizePath(root + os.sep + name)
            res = w.match(path)
            if res is not None:
                yield WalkFile(path, name, res)

        if len(s) == 0:
            break

#def copy(src, dst, copied = os.copy):
#    """ Copy all files matching src pattern to dst. If dst ends with backslash then it assumed as dir """
#    shutil.copy()

def _normalizePath(path):
    path = os.path.normcase(path)
    path = os.path.normpath(path)
    return path

def _splitWalkPath(path):
    # find first rough path
    regexp = r"(?P<root>([^%s]*\\)?)(?P<dirs>(.*\\)?)(?P<filename>.*)"
    regexp %= re.escape(''.join(_SYMBOLS))

    m = re.match(regexp, _normalizePath(path))
    return tuple(map(m.group, ("root", "dirs", "filename")))

_SYMBOLS = { '[', ']', '(', ')', '|', '?', '*' }

def translateWildcard(text, extractNames = False):
    # insert '\' before '.' and '\' symbols
    text = re.sub(r'(?P<char>[\\\.])',      r'\\\g<char>', text)

    # replace '*' wildcards with '.*' expression (except %*)
    text = re.sub(r'(?P<char>^|[^%])\*',    r'\g<char>.*', text)

    # replace '%c' pattern with '\c' expression
    text = re.sub(r'%(?P<char>.)',          r'\\\g<char>', text)

    # [name] pattern
    namedWildcard = re.compile(r'\[(?P<name>[\w\s]*)\]')

    # collect named wildcards
    names = set(m.group("name") for m in namedWildcard.finditer(text))

    # replace '[name]' pattern with '(?P<name>.*)' expression
    text = namedWildcard.sub(r'(?P<\g<name>>.*)', text)

    if extractNames:
        return text, names
    
    return text

class _Tests(TestCase):
    def checkFail(self, func):
        try:
            func()
        except:
            return
        self.fail()

    def makeResult(self, arr):
        res = []
        for p in arr:
            if p == '*':
                res.append(_WildcardRule.wildcard())
            elif isinstance(p, (tuple, list)):
                res.append(_WildcardRule.branch(list(map(self.makeResult, p))))
            elif p[0] == '[' and p[-1] == ']':
                res.append(_WildcardRule.wildcard(p[1:-1]))
            else:
                res.append(_WildcardRule.string(p))

        return res

    def testWildcardToRegexp(self):
        def check(wildcard, regexp):
            self.assertEqual(
                translateWildcard(wildcard), regexp)

        check('abc',            "abc")
        check("abc*de",         "abc.*de")
        check("(abc|de)",       "(abc|de)")
        check("(abc|)",         "(abc|)")
        check("(abc||de)",      "(abc||de)")
        check(r"a\b",           r"a\\b")
        check(r"[name].[ext]",  r"(?P<name>.*)\.(?P<ext>.*)")
        check("ab?",            r"ab?")

        check('abc[name]de*',   r"abc(?P<name>.*)de.*")

        check('abc(de*f)',      "abc(de.*f)")
        check(r"%*",            r"\*")
        check(r'a\b%)%(%[%]%|%?%*%%', r'a\\b\)\(\[\]\|\?\*\%')

    def testWildcard(self):
        def check(cond, text, match = True, **kw):
            w = Wildcard(cond)

            res = w.match(text)
            if not match:
                self.assertIsNone(res)
                return

            self.assertIsNotNone(res)

            for k, v in kw.items():
                self.assertEqual(res[k], v)

            self.assertEqual(len(res), len(kw))

        check("a*b",        r"anfjlshfls\c", False)
        check("a*b",        r"anfjlshfls\b")
        check(r"a\(b|c)",   r"a\b")
        check(r"a\(b|c)",   r"a\c")
        check(r"a\(b|c)",   r"a\d", False)

        check(r"a.b",       r"a.b")
        check(r"[path]\[name].[ext]",
              r"d:\my documents\some text file.txt",
              path = r"d:\my documents",
              name = "some text file",
              ext  = "txt")

        check(r"[name].[ext]", "dot.dot.dot.ext",
              name = "dot.dot.dot",
              ext = "ext")

    def testSplitWalkPath(self):
        def check(path, root, dirs, name):
            r, d, n = _splitWalkPath(path)
            self.assertEqual((r, d, n), (root, dirs, name))

        check(r"d:\test\*\filename.txt",        "d:\\test\\",   "*\\",      "filename.txt")
        check(r"d:\filename.txt",               "d:\\",         "",         "filename.txt")
        check(r"*\*.bak",                       "",             "*\\",      "*.bak")
        check(r"bin\*\in\[name]_original.(pyc?)",  "bin\\",     "*\\in\\",  "[name]_original.(pyc?)")

    def testWalk(self):
        STRUCTURE = [
            ('',    ['bin', 'src'], ['readme.txt', 'userpic.info']),
            ('bin', [],             ['exec.bat', 'my_runner.exe', 'other.exe']),
            ('src', ['in', 'out'],  []),
            ('src\\in', [],         ['%d.in' % index for index in range(3)]),
            ('src\\out', [],        ['res_%d' % index for index in range(2)]),
        ]
        def walker(path):
            for cur, dirs, files in STRUCTURE:
                root = os.path.normpath(path + os.sep + cur if len(path) > 0 else cur)
                yield root, dirs, files

        def check(path, result):
            arr = []
            for p in walk(path, walker = walker):
                arr.append(p.path)

            self.assertEqual(arr, result)

        check(r"d:\my documents.txt", [])
        check(r"d:\*.txt",      ['d:\\readme.txt'])
        check(r"d:\bin\*",      ['d:\\bin\\' + name for name in STRUCTURE[0][2]])
        check(r"d:\*\bin\*",    ['d:\\bin\\' + name for name in STRUCTURE[1][2]])
        files = []
        files.extend(['src\\in\\' + name for name in STRUCTURE[3][2]])
        files.extend(['src\\out\\' + name for name in STRUCTURE[4][2]])
        check(r"*\(in|out)\*",  files)
